package com.md.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.md.base.SerializableFunction;
import com.md.base.exception.ServerException;
import com.md.model.bo.TypeTransformation;
import com.md.value.enums.ErrorEnum;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static java.util.stream.Collectors.toList;

/**
 * 2021-11-01 15:58
 *
 * @author 刘骄阳
 */
@Slf4j
@UtilityClass
public class ClassUtils {
  private static final Map<String, BeanCopier> BEAN_COPIER_MAP = new ConcurrentHashMap<>();
  private static final Map<Class<?>, TypeTransformation<?>> CLASS_MAP = new HashMap<>();

  static {
    CLASS_MAP.put(String.class, TypeTransformation.of(String::valueOf, ""));
    CLASS_MAP.put(Boolean.class, TypeTransformation.of(Boolean::parseBoolean, false));
    CLASS_MAP.put(Integer.class, TypeTransformation.of(Integer::parseInt, 0));
    CLASS_MAP.put(Double.class, TypeTransformation.of(Double::parseDouble, 0.0));
    CLASS_MAP.put(BigDecimal.class, TypeTransformation.of(BigDecimal::new, new BigDecimal(0)));
    CLASS_MAP.put(Long.class, TypeTransformation.of(Long::parseLong, 0L));
    CLASS_MAP.put(Date.class, TypeTransformation.of(com.md.util.DateUtils::parse, new Date()));
    CLASS_MAP.put(LocalDateTime.class, TypeTransformation.of(DateUtils::parseLocalDateTime, LocalDateTime.now()));
    CLASS_MAP.put(LocalDate.class, TypeTransformation.of(com.md.util.DateUtils::parseLocalDate, LocalDate.now()));
    // todo 集合类型目前不止到如何初始化,后期进行探讨
    CLASS_MAP.put(List.class, TypeTransformation.of(json -> Lists.newArrayList(), Lists.newArrayList()));
    CLASS_MAP.put(Map.class, TypeTransformation.of(json -> Maps.newHashMap(), Maps.newHashMap()));
  }

  /**
   * 调用空参的私有方法
   *
   * @param clazz      哪一个类的私有方法
   * @param methodName 私有方法名
   * @param temp       从哪一个类调用
   * @return 操作结果
   */
  public static Object invokedMethod(final Class<?> clazz, final String methodName, final Object temp) {
    return invokedMethod(clazz, methodName, new LinkedHashMap<>(0), temp);
  }

  /**
   * 调用私有方法
   *
   * @param clazz      调用哪一个类的私有方法
   * @param methodName 调用的方法的名字
   * @param map        map,存储参数名,和类型
   * @param temp       从哪一个类里调用
   * @return 操作结果
   */
  public static Object invokedMethod(final Class<?> clazz, final String methodName,
                                     final Map<Object, Class<?>> map, final Object temp) {
    try {
      //从map中取出,键和值
      Class<?>[] propertyType = new Class<?>[map.size()];
      Object[] propertyValues = new Object[map.size()];
      int i = 0;
      for (final Map.Entry<Object, Class<?>> entry : map.entrySet()) {
        propertyValues[i] = entry.getKey();
        propertyType[i] = entry.getValue();
        i++;
      }
      //设置调用的方法,与参数类型
      Method method = clazz.getDeclaredMethod(methodName, propertyType);
      //设置可访问
      ReflectionUtils.makeAccessible(method);
      //调用,并传值
      return method.invoke(temp, propertyValues);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 调用私有方法
   *
   * @param clazz         调用哪一个类的私有方法
   * @param methodName    调用的方法的名字
   * @param propertyValue 参数值
   * @param propertyType  参数类型
   * @param temp          从哪一个类里调用
   * @return 操作结果
   */
  public static Object invokedMethod(final Class<?> clazz, final String methodName,
                                     final List<Object> propertyValue, final List<Class<?>> propertyType,
                                     final Object temp) {
    try {
      //从map中取出,键和值
      Class<?>[] propertyTypes = new Class<?>[propertyType.size()];
      Object[] propertyValues = new Object[propertyValue.size()];
      for (int i = 0; i < propertyType.size(); i++) {
        propertyTypes[i] = propertyType.get(i);
        propertyValues[i] = propertyValue.get(i);
      }
      //设置调用的方法,与参数类型
      Method method = clazz.getDeclaredMethod(methodName, propertyTypes);
      //设置可访问
      ReflectionUtils.makeAccessible(method);
      //调用,并传值
      return method.invoke(temp, propertyValues);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 调用私有方法
   *
   * @param clazz         调用哪一个类的私有方法
   * @param methodName    调用的方法的名字
   * @param propertyValue 参数值
   * @param temp          从哪一个类里调用
   * @return 操作结果
   */
  public static Object invokedMethod(final Class<?> clazz, final String methodName,
                                     final List<?> propertyValue, final Object temp) {
    try {
      //从map中取出,键和值
      Class<?>[] propertyTypes = new Class<?>[propertyValue.size()];
      Object[] propertyValues = new Object[propertyValue.size()];
      for (int i = 0; i < propertyValue.size(); i++) {
        propertyTypes[i] = propertyValue.get(i).getClass();
        propertyValues[i] = propertyValue.get(i);
      }
      //设置调用的方法,与参数类型
      Method method = clazz.getDeclaredMethod(methodName, propertyTypes);
      //设置可访问
      ReflectionUtils.makeAccessible(method);
      //调用,并传值
      return method.invoke(temp, propertyValues);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 获取私有属性的值
   *
   * @param propertyName 私有属性的名字
   * @param t            从哪一个类获取值
   * @return 私有属性的值
   */
  public static <T> Field getField(final String propertyName, final T t) {
    try {
      //通过类获取
      return t.getClass().getDeclaredField(propertyName);
    } catch (Exception e) {
      log.error("读取属性值失败: {}", e.getMessage(), e);
    }
    return null;
  }

  /**
   * 获取私有属性的值
   *
   * @param propertyName 私有属性的名字
   * @param t            从哪一个类获取值
   * @return 私有属性的值
   */
  public static <T> Object getFieldValue(final String propertyName, final T t) {
    try {
      //通过类获取
      Field field = t.getClass().getDeclaredField(propertyName);
      //设置可访问
      ReflectionUtils.makeAccessible(field);
      //参数为实例化对象
      return field.get(t);
    } catch (Exception e) {
      log.error("读取属性值失败: {}", e.getMessage(), e);
    }
    return "";
  }

  /**
   * 获取私有属性的值,指定类
   *
   * @param clazz        父类
   * @param propertyName 私有属性的名字
   * @param t            从哪一个类获取值
   * @return 私有属性的值
   */
  public static <T> Object getField(final Class<? super T> clazz, final String propertyName, final T t) {
    try {
      //通过类获取
      Field field = clazz.getDeclaredField(propertyName);
      //设置可访问
      ReflectionUtils.makeAccessible(field);
      //参数为实例化对象
      return field.get(t);
    } catch (Exception e) {
      log.error("读取属性值失败: {}", e.getMessage(), e);
    }
    return "";
  }

  /**
   * 将源的属性值,复制到target中
   *
   * @param source 源
   * @param target 目标
   * @param <T>    目标类型
   * @return 目标数据
   */
  public static <T> T source2Target(final Object source, final Class<T> target) {
    return source2Target(source, target, t -> {
    });
  }

  public static <T> T source2Target(final Object source, final Class<T> target, final Consumer<T> afterRun) {
    try {
      if (Objects.isNull(source)) {
        return null;
      }
      // 通过反射机制获取实例, 会执行静态方法和构造参数
      T t = target.getDeclaredConstructor().newInstance();
      // 将source的类与target的类当key
      String key = source.getClass() + target.toString();
      BeanCopier beanCopier;
      // 判断是否存在,如果存在则从map中取,防止频繁创建对象
      // BeanCopier的原理是通过字节码动态生成一个类，这个里面实现get 和 set方法。(性能和对象直接调用set一样)
      // 获取setter方法时，只会获取返回类型为void的setter方法
      if (BEAN_COPIER_MAP.containsKey(key)) {
        beanCopier = BEAN_COPIER_MAP.get(key);
      } else {
        beanCopier = BeanCopier.create(source.getClass(), target, false);
        BEAN_COPIER_MAP.put(key, beanCopier);
      }
      // 拷贝
      beanCopier.copy(source, t, null);
      afterRun.accept(t);
      return t;
    } catch (Exception e) {
      log.error("拷贝异常: {}", e.getMessage(), e);
      throw ServerException.of(ErrorEnum.COPY_ERROR);
    }
  }

  /**
   * 将源的属性值,复制到target中
   *
   * @param source        源
   * @param functionArray 要克隆的属性值
   * @param <T>           目标类型
   * @return 目标数据
   */
  @SuppressWarnings("unchecked")
  @SafeVarargs
  public static <T> T copyProperty(final T source, final SerializableFunction<T, ?>... functionArray) {
    try {
      if (ObjectUtil.isEmpty(source)) {
        return null;
      }
      Class<?> sourceClass = source.getClass();
      Map<String, PropertyDescriptor> entityProperty = getEntityProperty(sourceClass,
        propertyDescriptor -> propertyDescriptor.getReadMethod().getName());
      Object target = sourceClass.getDeclaredConstructor().newInstance();
      for (final SerializableFunction<T, ?> value : functionArray) {
        try {
          SerializedLambda serializedLambda = getSerializedLambda(value);
          String fieldName = serializedLambda.getImplMethodName();
          // 通过反射获取字段
          Field field = getClassField(sourceClass, entityProperty.get(fieldName).getName());
          ReflectionUtils.makeAccessible(field);
          ReflectionUtils.setField(field, target, value.apply(source));
        } catch (Exception e) {
          log.error("克隆属性失败: {}", e.getMessage(), e);
        }
      }
      return (T) target;
    } catch (Exception e) {
      log.error("拷贝异常: {}", e.getMessage(), e);
      throw ServerException.of(ErrorEnum.COPY_ERROR);
    }
  }

  public Field getClassField(final Class<?> sourceClass,
                             final String fieldName) {
    try {
      return sourceClass.getDeclaredField(fieldName);
    } catch (NoSuchFieldException e) {
      Class<?> superclass = sourceClass.getSuperclass();
      if (ObjectUtil.isEmpty(superclass)) {
        return null;
      }
      return getClassField(superclass, fieldName);
    }
  }

  public <T> Map<T, PropertyDescriptor> getEntityProperty(final Class<?> sourceClass,
                                                          final SerializableFunction<PropertyDescriptor, T> lambda) {
    Map<T, PropertyDescriptor> map = Maps.newHashMap();
    PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(sourceClass);
    for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      map.put(lambda.apply(propertyDescriptor), propertyDescriptor);
    }
    return map;
  }

  private static <T, R> SerializedLambda getSerializedLambda(final Function<T, R> lambda)
    throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Method writeReplace = lambda.getClass().getDeclaredMethod("writeReplace");
    ReflectionUtils.makeAccessible(writeReplace);
    return (SerializedLambda) writeReplace.invoke(lambda);
  }

  public static <T> List<T> source2Target(final List<?> source, final Class<T> target) {
    return source2Target(source, target, (s, t) -> {
    });
  }

  public static <T, E> List<T> source2Target(final List<E> source,
                                             final Class<T> target,
                                             final BiConsumer<E, T> afterRun) {
    if (ObjectUtil.isEmpty(source)) {
      return Lists.newArrayList();
    }
    return source.stream().map(e -> {
      T t = source2Target(e, target);
      afterRun.accept(e, t);
      return t;
    }).collect(toList());
  }

  /**
   * 获取注解
   *
   * @param proceedingJoinPoint proceedingJoinPoint
   * @param clazz               clazz
   * @param <T>                 类型
   * @return 指定类型的注解
   */
  public static <T extends Annotation> T getAnnotation(final ProceedingJoinPoint proceedingJoinPoint,
                                                       final Class<T> clazz) {
    MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
    return methodSignature.getMethod().getAnnotation(clazz);
  }


  /**
   * 校验类型,获取对应类型的值
   *
   * @param type  类型
   * @param value 值
   * @return 对应类型的值
   */
  public static Object judgeClassType(final Supplier<Class<?>> type,
                                      final Supplier<?> value) throws ReflectiveOperationException {
    return judgeClassType(type, value.get());
  }

  /**
   * 校验类型,获取对应类型的值
   *
   * @param type  类型
   * @param value 值
   * @return 对应类型的值
   */
  public static Object judgeClassType(final Supplier<Class<?>> type,
                                      final Object value) throws ReflectiveOperationException {
    Class<?> clazz = type.get();
    TypeTransformation<?> typeTransformation = CLASS_MAP.get(clazz);
    if (typeTransformation == null) {
      log.error("未定义的类型{}", clazz.getName());
      Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
      ReflectionUtils.makeAccessible(declaredConstructor);
      return declaredConstructor.newInstance();
    }
    return typeTransformation.build(value);
  }
}
